闭包是一种特殊的对象/特殊的机制。
闭包由两部分组成:执行上下文(A)和在该执行上下文中创建的函数(B);
当B执行时访问了A中变量对象中的值,就产生了闭包;
另一个解释:闭包是一项技术或者一个特性,函数作用域中的变量在函数执行完之后就会被垃圾回收,一般情况下访问一个函数作用域中的变量是无法访问的,只能通过特殊的技术或者特性来实现,就是在函数作用域中创建内部函数来实现,这样就不会使得函数执行完成变量被回收,这种技术或特性被称为闭包。像是把变量包裹了起来,形象的称为闭包。
经典应用
- 利用闭包,修改下面的代码,让循环输出的结果依次为1,2,3,4,5
源代码:
for(let i = 1; i <= 5; i++){
setTimeout(function timer(){
console.log(i);
},i*1000)
}
在这个例子中,首先会运行for循环,每趟运行到setTimeout的时候,就将它放入任务队列,当5次for循环完成之后,再去运行任务队列中的定时任务,此时定时任务的词法环境中不存在i,就向上寻找到for循环结束时的i为5,然后定时输出五个5。
**其实这个可以正确输出,因为let形成了块级作用域
修改:
for(let i = 1; i <= 5; i++){
(function(i){
setTimeout(function timer(){
console.log(i);
})
},i*1000)(i)
}
原理:借助闭包的特性,使用一个自执行函数提供闭包条件,每次传入i值保存在闭包中。
还可以写成:
for(let i = 1; i <= 5; i++){
setTimeout((function(i){
return function(){
console.log(i);
}
})(i),i*1000)
}
- 柯里化
柯里化(currying)是一种关于函数的高阶技术,它不仅用于Javascript,还被用于其他编程语言。
柯里化是一种函数的转换,它是指将一个函数从可调用的f(a,b,c)转换为可调用的f(a)(b)(c)。他不会调用函数,只是对函数进行转换。
一个柯里化的函数首先会接收一些参数,接受了这些参数之后,该函数不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数中形成闭包被保存起来,待到函数真正需要求值的时候,之前传入的参数会被用于一次性求值。
简单例子:
function sum(a){
return function(b){
return a+b;
}
}
console.log(sum(1)(2));
//3
在这个例子里,sum首先接受了一个参数a,将其存在闭包里,在接收另一个参数b之后才进行计算。
将柯里化过程封装成柯里化函数:
function curry(f){
return function(a){
return function(b){
return f(a,b);
}
}
}
function sum(a,b){
return a+b;
}
let curriedSum = curry(sum);
console.log(curriedSum(1)(2));
//3
在上面这个例子里,curriedSum实际上是一个包装器function(a),当他被curriedSum(1)调用时,返回一个包装器function(b),并将a存在词法环境中,当function(b)以参数2被调用时,就会把参数传递给原始的sum函数。闭包体现在function(b)能够取到参数a的值。
高级柯里化:
function curry(f){
return function curried(...args){
//f.length是函数f定义时的参数个数
if(args.length >= f.length){
return f.apply(this,args);
}else{
return function(...args2){
return curried.apply(this,args.concat(args2));
}
}
}
}
function sum(a,b,c){
return a+b+c;
}
let curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3));
//6
console.log(curriedSum(1,2)(3));
//6
当运行柯里化函数时,有两个if分支,如果传入的args长度与原始定义的f.length相同或更长,那么只需要使用f.apply将调用传递给他即可;否则返回一个包装器,重新应用curried,将之前传入的参数与新的参数一起传入。